/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tuplestoreserver;


import core.AbstractCoreClient;
import core.AbstractCoreServer;
import core.Callback;
import core.IServerCallBack;
import core.ProtocolSD;
import core.ProtocolSD.IProtocolListener;
import core.Result;
import core.ServerCallBack;
import core.ThreadPool;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.channels.SelectionKey;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import logging.LOG;
import protocol.TSFile;
import protocol.TSFileTag;
import protocol.TSServerInfo;

/**
 *
 * @author Jorge
 */
public class Server {

    public final static String MASTER_IP ="localhost";
    public final static int MASTER_PORT = 3500;

 

    /** Mapeamento entre chave ficheiro*/
    private Map<Integer, TSFileTag> keyMapping = new HashMap<Integer, TSFileTag>();

    private AbstractCoreServer clientServer;

    private AbstractCoreClient masterClient;

    private AbstractCoreServer masterServer;

    private TSServerInfo serverInfo;

    private ThreadPool pool = new ThreadPool(10);


    public  Server(int clientPort,int serverPort) {
        open();
        serverInfo=new TSServerInfo(0, "localhost", serverPort, clientPort);
        initMasterClient();
        initMasterServer(serverPort);
        initServerComunication();

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                save();
            }
        }, 60000,10000);

        Timer tImer1 = new Timer();
        tImer1.schedule(new TimerTask() {

            @Override
            public void run() {
                Callback<byte[],Long> callback = new Callback<byte[], Long>() {

            public void onError(Exception e) {
                throw new RuntimeException();
            }

            public void onResult(Result<byte[], Long> v) {
                

                initClientServer(serverInfo.getServerPort());
            }
        };

                Server.this.connect(MASTER_IP, MASTER_PORT, getStatusCallBack(callback));
            }
        }, 60000*5,60000*5);
       
    }

    public void save(){
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream("data.dat.tmp");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(serverInfo);
            objectOutputStream.writeObject(keyMapping);
            objectOutputStream.close();
            fileOutputStream.close();
            File file = new File("data.dat.tmp");
            File file1 = new File("data.dat");
            if(file1.exists())
                file1.delete();
            file.renameTo(new File("data.dat"));
        } catch (IOException ex) {
            return;
        } finally {
            try {
                if(fileOutputStream!=null)
                fileOutputStream.close();
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }



    public void open(){
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream("data.dat");
            ObjectInputStream objectOutputStream = new ObjectInputStream(inputStream);
            serverInfo = (TSServerInfo) objectOutputStream.readObject();
            keyMapping = (Map<Integer, TSFileTag>) objectOutputStream.readObject();
            objectOutputStream.close();
            inputStream.close();
            
        } catch (Exception ex) {
            return;
        } finally {
            try {
                if(inputStream!=null)
                inputStream.close();
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

    }


    public Map<Integer, TSFileTag> getKeyMapping() {
        return keyMapping;
    }

    

    private void initServerComunication(){
        Callback<byte[],Long> callback = new Callback<byte[], Long>() {

            public void onError(Exception e) {
                throw new RuntimeException();
            }

            public void onResult(Result<byte[], Long> v) {
                

                initClientServer(serverInfo.getServerPort());
            }
        };

        masterClient.connect(MASTER_IP, MASTER_PORT,getStatusCallBack(callback));
    }

    private ServerCallBack<byte[],Long> getStatusCallBack(Callback<byte[],Long> callback){

        return  new ServerCallBack<byte[],Long>(callback) {

            public void onFinishClient(ProtocolSD protocolSD) {
                protocolSD.write(new byte[]{(byte)ProtocolSD.Operation.STATUS.ordinal(),(byte)7});

                byte[] bs = new byte[3];
                byte[] bs1 = new byte[3];
                byte[] bs12 = new byte[1];

                List<Integer> list = new LinkedList<Integer>();
                List<Long> list1 = new LinkedList<Long>();
                List<String> list12 = new LinkedList<String>();

                bs1[0]= (byte)TSServerInfo.FIELD.AVAILABLE_SIZE.ordinal();
                list1.add(serverInfo.getAvailableSize());
                bs[0]= (byte)TSServerInfo.FIELD.COMUNICATION_PORT.ordinal();
                list.add(serverInfo.getComunicationPort());
                bs1[1]= (byte)TSServerInfo.FIELD.KEY_SIZE.ordinal();
                list1.add(serverInfo.getKeySize());
                bs12[0]= (byte)TSServerInfo.FIELD.SERVER_ADDRESS.ordinal();
                list12.add(serverInfo.getServerAddress());
                bs[1]= (byte)TSServerInfo.FIELD.SERVER_PORT.ordinal();
                list.add(serverInfo.getServerPort());
                bs[2]= (byte)TSServerInfo.FIELD.UNIQUE_ID.ordinal();
                list.add(serverInfo.getUniqueID());
                bs1[2]= (byte)TSServerInfo.FIELD.USED_SIZE.ordinal();
                list1.add(serverInfo.getUsedSize());

                protocolSD.writeInt(bs,list);
                protocolSD.writeLong(bs1,list1);
                protocolSD.writeString(bs12, list12);

                protocolSD.getKey().interestOps(SelectionKey.OP_WRITE);
            }

            @Override
            public void onReadArgument(byte[] data, ProtocolSD protocolSD) {   
                    serverInfo.setUniqueID(ProtocolSD.byteArrayToInt(data, 0));
                
            }

            @Override
            public void onWriteComplete(ProtocolSD protocolSD1) {
                //Espera por resposta
                protocolSD1.getKey().interestOps(SelectionKey.OP_READ);
            }

            @Override
            public void onReadAllArguments(ProtocolSD protocolSD) {
                ((ServerCallBack)this).onFinish();
            }



        };
    
    }


    public void initClientServer(int port) {
        clientServer = new AbstractCoreServer() {

            @Override
            protected IProtocolListener createInstance() {
                return new ClientListener(Server.this);
            }

            @Override
            public void handlerServerError(Throwable exception) {
                System.out.println(Thread.currentThread().getName()+" Erro de servidor " + exception.getMessage());
            }

            @Override
            public void handlerFatalErrorCliente(Throwable throwable) {
                System.out.println(Thread.currentThread().getName()+"Erro cliente " + throwable.getMessage());
            }
        };
        clientServer.setName("servidor dos clientes");
        clientServer.addUnJoinParam(ProtocolSD.Operation.PUT_DATA);
        clientServer.connect(port);
    }

    public void initMasterServer(int port) {
        masterServer = new AbstractCoreServer() {

            @Override
            protected IProtocolListener createInstance() {
                return new MasterServerListener(Server.this);
            }

            @Override
            public void handlerServerError(Throwable exception) {
                System.out.println(Thread.currentThread().getName()+"Erro de servidor " + exception.getMessage());
            }

            @Override
            public void handlerFatalErrorCliente(Throwable throwable) {
                System.out.println(Thread.currentThread().getName()+"Erro cliente " + throwable.getMessage());
            }
        };
        masterServer.setName("servidor do master");
        masterServer.connect(port);
    }

    public void initMasterClient() {
        masterClient = new AbstractCoreClient() {

            @Override
            protected IProtocolListener createInstance() {
                return new MasterClientListener();
            }

            @Override
            public void handlerServerError(Throwable exception) {
                System.out.println(Thread.currentThread().getName()+"Erro de servidor " + exception.getMessage());
            }

            @Override
            public void handlerFatalErrorCliente(Throwable throwable) {
                System.out.println(Thread.currentThread().getName()+"Erro cliente " + throwable.getMessage());
            }

            @Override
            public void handleFinishConnect(SelectionKey key) {
                System.out.println(Thread.currentThread().getName()+"Cliente ligado com sucesso");
            }


        };
             masterClient.setName("cliente do servidor");
        masterClient.connect();
    }

    

    public void connect(String host,int port,IServerCallBack callback){
        masterClient.connect(host, port, callback);
    }

    public int getServerKey() {
        return serverInfo.getUniqueID();
    }



    public void removeKey(int key) {
        LOG.append("remove key");
        TSFileTag fileTag = null;
        synchronized(keyMapping){
            fileTag = keyMapping.remove(key);
        }
        if(fileTag!=null){
            serverInfo.setKeySize(serverInfo.getKeySize());
        }
         File file = new File(key+"");
         file.delete();
    }

    public synchronized  void commitFileTmpKey(TSFile file,boolean b){
        LOG.append((b?"Commit da chave ":"Commit inválido ")+file.getFileTag().getKey());
        if(b){
            if(!keyMapping.containsKey(file.getFileTag().getKey())){
                serverInfo.setKeySize(serverInfo.getKeySize());
            }
            
            keyMapping.put(file.getFileTag().getKey(),file.getFileTag());
        }
        pool.closeFile(file, b);
        
    }

    public TSFile getReadFile(int key){
        return pool.getReadFile(key);
    }

    public  TSFile getNewFileFromKey(int key,int ticket,int size){
        try {
            TSFile file = new TSFile(key, ticket,size);
            pool.putFile(file);
            return file;
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return null;
    }
    

    public  void drainSocketToFile(int key, byte[] bs,int size,TSFile file){              
            pool.putJob(file, bs);
    }

    public  void drainFileToSocket(ProtocolSD protocolSD,TSFile key, Callback<Integer,Boolean> callback){
        try {
            protocolSD.setFileChannel(key.getReadChannel(), key.getFileTag().getFileSize());
        } catch (IOException ex) {
            throw new RuntimeException();
            //desliga cliente
        }
    }



}
